﻿using System;
using System.Collections.Generic;
using System.Linq;
using StockholmsStad.Data.Context;
using StockholmsStad.Data.ContractFactory;
using StockholmsStad.Data.Entity;
using StockholmsStad.Data.Interfaces;
using StockholmsStad.Runtime.Serialization.DataContract;
using StockholmsStad.Utilities.Enums;
using StockholmsStad.Utilities.Exceptions;
using Booking=StockholmsStad.Data.Entity.Booking;
using Minister=StockholmsStad.Runtime.Serialization.DataContract.Minister;
using Ticket=StockholmsStad.Data.Entity.Ticket;

namespace StockholmsStad.Data.DatabaseAgents
{
    internal class MinisterAgent : DBAgentBase, IMinisterAgent
    {
        #region Implementation of IMinisterAgent

        public int AddMinister(IDataContext context, Minister minister)
        {
            MinisterFactory factory = FactoryAgent.GetMinisterFactory();
            Entity.Minister entityMinister = factory.CreateEntityObject(minister);
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                Entity.Minister ministerWithBookingCode =
                    dbCtx.Ministers.Where(m => m.MinisterCode == minister.MinisterCode).FirstOrDefault();
                if (ministerWithBookingCode != null)
                    throw new StockholmStadSystemException(SystemErrorCode.MinisterCodeAlreadyExists);
                dbCtx.Ministers.InsertOnSubmit(entityMinister);
                dbCtx.SubmitChanges();

                if (minister.Languages != null)
                {
                    //Uppdatera språken
                    UpdateMinisterLanguages(context, entityMinister.MinisterId, minister.Languages);
                }
                return entityMinister.MinisterId;
            }
            catch (StockholmStadApplicationException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        /// <summary>
        /// Uppdaterar en förättare
        /// </summary>
        /// <param name="context">Det databascontext som operationen köra i</param>
        /// <param name="ministerId"></param>
        /// <param name="minister"></param>
        public void UpdateMinister(IDataContext context, int ministerId, Minister minister)
        {
            try
            {
                if (minister.Languages != null)
                {
                    //Uppdatera språken
                    UpdateMinisterLanguages(context, ministerId, minister.Languages);
                }

                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                Entity.Minister entityMinister = dbCtx.Ministers.Where(m => m.MinisterId == ministerId).FirstOrDefault();
                if (entityMinister == null)
                    throw new StockholmStadSystemException(SystemErrorCode.MinisterNotExists);
                MinisterFactory factory = FactoryAgent.GetMinisterFactory();
                factory.UpdateEntityObject(minister, entityMinister);
                dbCtx.SubmitChanges();

                if (minister.Languages != null)
                {
                    //Uppdatera språken
                    UpdateMinisterLanguages(context, entityMinister.MinisterId, minister.Languages);
                }
            }
            catch (StockholmStadApplicationException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public List<Minister> GetMinisters(IDataContext context, string partOfName, 
            DateTime? assignmentStartDate, DateTime? assignmentEndDate, bool isActive)
        {
            var factory = new MinisterFactory();
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                string startDate = string.Empty;
                if (assignmentStartDate.HasValue)
                    startDate = assignmentStartDate.Value.ToString("yyyy-MM-dd");

                string endDate = string.Empty;
                if (assignmentEndDate.HasValue)
                    startDate = assignmentEndDate.Value.ToString("yyyy-MM-dd");

                var ministers = dbCtx.
                    FindMinisters(partOfName, startDate, endDate, isActive).
                    Select(m => factory.CreateContractMinister(m)).ToList();

                for (int i = 0; i < ministers.Count; i++)
                {
                    List<ValueIdPair> languages = GetMinisterLanguages(context, ministers[i].MinisterId.Value);
                    if (languages.Count > 0)
                    {
                        factory.CreateContractLanguages(ministers[i], languages);
                    }
                }

                return ministers;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public void SendMinisterServiceReminderMail(IDataContext context, string recipientEmail, Runtime.Serialization.DataContract.Ticket ticket, DateTime startDateTime)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                var person1 = string.Format("{0} {1}", ticket.Couple.Person1.FirstNames, ticket.Couple.Person1.LastName);
                var person2 = string.Format("{0} {1}", ticket.Couple.Person2.FirstNames, ticket.Couple.Person2.LastName);

                var coupleRegister = startDateTime.ToString("yyyy-MM-dd hh:m:ss") + "," + person1 + "," + person2 + ";";
                
                dbCtx.SendMinisterServiceReminderMail(recipientEmail, startDateTime, coupleRegister);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public List<Minister> GetMinisters(IDataContext context, DateTime date)
        {
            try
            {
                var factory = new MinisterFactory();
                var dbCtx = context.GetInternalContext();

                List<Minister> ministers =
                    dbCtx.Ministers.Where(m => date.CompareTo(m.AssignmentStartDate) >= 0 && date.CompareTo(m.AssignmentEndDate) <= 0).
                        Select(m => factory.CreateContractObject(m)).ToList();

                return ministers;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public List<Minister> GetMinisters(IDataContext context)
        {
            try
            {
                return GetMinisters(context, string.Empty, null, null, true);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public int? GetMinisterIdByMinisterCode(IDataContext context, string ministerCode)
        {
            Entity.Minister minister = GetMinisterEntityByMinisterCode(context, ministerCode);
            return minister == null ? (int?) null : minister.MinisterId;
        }

        public Minister GetMinister(IDataContext context, int ministerId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var entityMinister = dbCtx.Ministers.Where(m => m.MinisterId == ministerId).FirstOrDefault();

                if (entityMinister != null)
                {
                    var factory = new MinisterFactory();
                    var minister = factory.CreateContractObject(entityMinister);
                    List<ValueIdPair> languages = GetMinisterLanguages(context, ministerId);
    
                    if(languages != null && languages.Count > 0)
                        factory.CreateContractLanguages(minister, languages);
                    
                    return minister;
                }
            }
            catch (Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }

            return null;            
        }

        public Minister GetMinisterByTicket(IDataContext context, int ticketId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var ministerByTicket = dbCtx.GetMinisterByTicket(ticketId).FirstOrDefault();

                if (ministerByTicket != null)
                {
                     var factory = new MinisterFactory();
                     return factory.CreateContractObject(ministerByTicket);
                }
            }
            catch(Exception e)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, e);
            }

            return null;
        }

        public Minister GetMinisterByMinisterCode(IDataContext context, string ministerCode)
        {
            Entity.Minister minister = GetMinisterEntityByMinisterCode(context, ministerCode);
            if (minister == null)
                return null;

            List<ValueIdPair> languages = GetMinisterLanguages(context, minister.MinisterId);
            var factory = new MinisterFactory();
            Minister contractObject = factory.CreateContractObject(minister);
            factory.CreateContractLanguages(contractObject, languages);
            return contractObject;
        }

        public void RemoveMinister(IDataContext context, int ministerId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                Entity.Minister minister = dbCtx.Ministers.Where(m => m.MinisterId == ministerId).FirstOrDefault();
                if (minister != null)
                {
                    // Ta bort språken för förrättaren
                    IEnumerable<MinisterLanguage> languages =
                        dbCtx.MinisterLanguages.Where(l => l.MinisterId == minister.MinisterId);
                    foreach (MinisterLanguage lang in languages)
                    {
                        dbCtx.MinisterLanguages.DeleteOnSubmit(lang);
                        dbCtx.SubmitChanges();
                    }

                    dbCtx.Ministers.DeleteOnSubmit(minister);
                    dbCtx.SubmitChanges();
                }
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public List<MinisterTimeBlock> GetTimeBlocksForMinister(IDataContext context, int ministerId)
        {
            try
            {
                var factory = new MinisterFactory();
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var timeblocks = dbCtx.GetMinisterTimeBlocks(ministerId).ToList();
                return timeblocks.Select(t => factory.CreateContractTimeBlock(t)).ToList();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public bool HasMinisterAnyServices(IDataContext context, int ministerId, DateTime date)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var ceremonies = dbCtx.FindCeremoniesForMinister(ministerId, date).FirstOrDefault();

                if(ceremonies != null && ceremonies.NumberOf.HasValue)
                    return ceremonies.NumberOf > 0 ? true : false;

                return false;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }            
        }

        #endregion

        #region private methods
        private static List<ValueIdPair> GetMinisterLanguages(IDataContext context, int ministerId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                CeremonyFactory ceremonyFactory = FactoryAgent.GetCeremonyFactory();
                List<ValueIdPair> languages = (from l in dbCtx.Languages
                                               join ml in dbCtx.MinisterLanguages on l.LanguageId equals ml.LanguageId
                                               where ml.MinisterId == ministerId
                                               orderby l.LanguageId
                                               select ceremonyFactory.CreateContractObject(l)).ToList();
                return languages;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        private void UpdateMinisterLanguages(IDataContext context, int ministerId, IEnumerable<string> languages)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                var newLanguages = new List<int>();
                foreach (string languageName in languages)
                {
                    Language dbLang = SelectAll<Language>(dbCtx).Find(l => l.Name.Equals(languageName));

                    if (dbLang != null)
                    {
                        newLanguages.Add(dbLang.LanguageId);
                    }
                    else
                    {
                        var newLanguage = new Language {Name = languageName};
                        dbCtx.Languages.InsertOnSubmit(newLanguage);
                        dbCtx.SubmitChanges();
                        newLanguages.Add(newLanguage.LanguageId);
                    }
                }

                //StockholmsStadDataContext dbCtx = context.GetInternalContext();
                // Tar fram det gamla och det nya
                IEnumerable<int> oldLanguages = GetMinisterLanguages(context, ministerId).Select(l => l.Id);
                //IEnumerable<int> newlanguages = languageIds;

                // commonLanguages ska inte åtgärdas
                IEnumerable<int> commonLanguages = oldLanguages.Intersect(newLanguages);
                // tar fram de som tas bort
                IEnumerable<int> languagesToRemove = oldLanguages.Except(commonLanguages);
                // tar fram de som ska sättas in
                IEnumerable<int> languagesToInsert = newLanguages.Except(commonLanguages);

                // ta bort
                dbCtx.MinisterLanguages.DeleteAllOnSubmit(
                    dbCtx.MinisterLanguages.Where(ml => languagesToRemove.Contains(ml.LanguageId)
                                                    && ml.MinisterId == ministerId));

                // sätt in nya
                var factory = new MinisterFactory();
                dbCtx.MinisterLanguages.InsertAllOnSubmit(
                    languagesToInsert.Select(l => factory.CreateEntityObject(l, ministerId)));

                dbCtx.SubmitChanges();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        private static Entity.Minister GetMinisterEntityByMinisterCode(IDataContext context, string ministerCode)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                Entity.Minister minister = dbCtx.Ministers.Where(m => m.MinisterCode == ministerCode).FirstOrDefault();
                if (minister == null)
                    return null;
                return minister;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        #endregion
    }
}